package temp;

/********************************************************************************
* BankClienAgent:
* ---------------
* This class is an example showing how to use the classes OneShotBehaviour,
* SimpleBehaviour, WakerBehaviour and ParallelBehaviour to program an agent.
*
* Version 1.0 - July 2003
* Author: Ambroise Ncho, under the supervision of Professeur Jean Vaucher
*
* Universite de Montreal - DIRO
*
*********************************************************************************/

import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.OneShotBehaviour;
import jade.core.behaviours.ParallelBehaviour;
import jade.core.behaviours.SimpleBehaviour;
import jade.core.behaviours.WakerBehaviour;
import jade.domain.DFService;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.util.leap.ArrayList;
import jade.util.leap.Iterator;
import jade.util.leap.List;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.rmi.server.Operation;

public class BankClientAgent extends Agent implements BankVocabulary {
// -------------------------------------------------------------------

   static final int WAIT = -1;
   static final int QUIT = 0;
   private int command = WAIT;
   private int cnt = 0;
   private AID server;
   private List accounts = new ArrayList();

   protected void setup() {

   // Set this agent main behaviour
      addBehaviour(new WaitUserCommand(this));
   }

   class WaitUserCommand extends OneShotBehaviour {
// ------------------------------------------------  Get the user command

      WaitUserCommand(Agent a) {
		 super(a);
		 command = WAIT;
	  }

      public void action() {

         command = getUserChoice();

         if (command == QUIT) {
		    System.out.println(getLocalName() + " is shutting down...Bye!");
		    doDelete();
		    System.exit(0);
		 }
		 if (command == NEW_ACCOUNT)
		    createAccount();

		 else if (command == DEPOSIT || command == WITHDRAWAL)
		    requestOperation();

		 else if (command == BALANCE || command == OPERATIONS)
			queryInformation();

		 else {
			System.out.println("Invalid choice!");
		    addBehaviour(new WaitUserCommand(myAgent));
		 }
	  }
   }


   void createAccount() {
// ----------------------  Process to the server agent the request
//                         to create a new account

	  CreateAccount ca = new CreateAccount();
	  ca.setName("acc" + cnt++);
	  sendMessage(ACLMessage.REQUEST, ca);
   }


   void requestOperation() {
// -------------------------  Process to the server agent the request
//                            to make an operation

      String accName = chooseAccount();
	  if (accName == null) {
		 addBehaviour(new WaitUserCommand(this));
		 return;
	  }
      Account acc = null;
	  if ((acc = findAccountByName(accName)) == null) {
		 System.out.println("Invalid account!");
		 addBehaviour(new WaitUserCommand(this));
		 return;
	  }
      String in = getUserInput("\nAmount: ");
	  if (in == null) {
		 System.out.println("Unable to process operation!");
		 return;
	  }
	  float amount = 0;
	  amount = Float.parseFloat(in);
	  MakeOperation mop = new MakeOperation();
	  mop.setAccountId(acc.getId());
	  mop.setType(command);
	  mop.setAmount(amount);
	  sendMessage(ACLMessage.REQUEST, mop);
   }

   void queryInformation() {
// -------------------------   Process to the server agent the request
//                             a query for information

      String accName = chooseAccount();
      Account acc = null;
	  if (accName == null || (acc = findAccountByName(accName)) == null) {
		 System.out.println("Invalid account!");
		 addBehaviour(new WaitUserCommand(this));
		 return;
	  }
	  String s = " charge applies for this operation. Continue?\ny/n: ";
	  s = command==BALANCE ? "\n\n\t$" + BAL_CHARGE + s:
	                         "\n\n\t$" + OPER_CHARGE + s;
	  String in = getUserInput(s);
	  if (in.equals("y")) {
	     Information info = new Information();
	     info.setType(command);
	     info.setAccountId(acc.getId());
	     sendMessage(ACLMessage.QUERY_REF, info);
	     return;
	  }
	  if (in.equals("n")) System.out.println("\nOperation aborted!");
	  else System.out.println("\nInvalid choice!");
	  addBehaviour(new WaitUserCommand(this));
   }


   class WaitServerResponse extends ParallelBehaviour {
// ----------------------------------------------------  launch a SimpleBehaviour to receive
//                                                       servers response and a WakerBehaviour
//                                                       to terminate the waiting if there is
//                                                       no response from the server
      WaitServerResponse(Agent a) {

	     super(a, 1);

	     addSubBehaviour(new ReceiveResponse(myAgent));

	     addSubBehaviour(new WakerBehaviour(myAgent, 5000) {

		    protected void handleElapsedTimeout() {
			   System.out.println("\n\tNo response from server. Please, try later!");
			   addBehaviour(new WaitUserCommand(myAgent));
			}
		 });
	  }
   }


   class ReceiveResponse extends SimpleBehaviour {
// -----------------------------------------------  // Receive and handle server responses

      private boolean finished = false;

      ReceiveResponse(Agent a) {
	     super(a);
	  }

	  public void action() {

	     ACLMessage msg = receive(MessageTemplate.MatchSender(server));
	     if (msg == null) { block(); return; }

		 if (msg.getPerformative() == ACLMessage.NOT_UNDERSTOOD){
		    System.out.println("\n\tResponse from server: NOT UNDERSTOOD!");
		 }
		 else {
	        try {
		       Object content = msg.getContentObject();

		       if (content instanceof Problem) {
		          System.out.println("\n\n\tResponse from server: " +
		                            ((Problem)content).getMsg().toUpperCase());
			   }

               //-------------------- Create a new accont -------------------------

			   else if (command == NEW_ACCOUNT) {

		          Account acc = (Account) content;
		          accounts.add(acc);
		          System.out.println("\n\n\tResponse from server:\nAccount [" +
		                             acc.toString() + "] created!");
		       }
		       //---------------- Make deposit or withdrawal -----------------------

		       else if (command == DEPOSIT || command == WITHDRAWAL) {

		          if (msg.getPerformative() == ACLMessage.INFORM){
			         if (content instanceof Account) {
			            Account acc = (Account)content;
		                System.out.println("\n\n\tResponse from server:\nAccount [" +
		                                   acc.toString() + "]");
			         }
			         else System.out.println("\nUnexpected msg content from server!");
		          }
		          else  System.out.println("\nUnexpected msg from server!");
	           }
	           //---------------- Get Balance or operations list --------------------

		       else if (command == BALANCE || command == OPERATIONS) {

	              if (msg.getPerformative() == ACLMessage.INFORM){
			         if (content instanceof Account) {
			            Account acc = (Account)content;
		                System.out.println("\n\n\tResponse from server:\nAccount [" +
		                                   acc.toString() + "]");
			         }
			         else if (content instanceof OperationList) {
			            List list = ((OperationList) content).getOperations();
			            String accId = ((OperationList) content).getAccountId();
			            Account acc = findAccountById(accId);
			            String s = "\n\n\tLIST OF OPERATIONS: Account [" + acc.getName() +
			                       " # " + acc.getId() + "]\n\tDate\tType\tAmount\tBalance";
			            for (Iterator it = list.iterator(); it.hasNext();)
                           s += ((Operation)it.next()).toString();
			            System.out.println(s);
			         }
			         else System.out.println("\nUnexpected msg content from server!");
			      }
		          else  System.out.println("\nUnexpected msg from server!");
		       }
		    }
		    catch (Exception e) { e.printStackTrace(); }
		 }
	     finished = true;
	  }

	  public boolean done() { return finished; }
	  
	  

	  public int onEnd() {
	     addBehaviour(new WaitUserCommand(myAgent));
	     return 0;
	  }
   }


   void lookupServer() {
// ---------------------  Search in the DF to retrieve the server AID

	  ServiceDescription sd = new ServiceDescription();
      sd.setType(SERVER_AGENT);
	  DFAgentDescription dfd = new DFAgentDescription();
      dfd.addServices(sd);
      try {
	     DFAgentDescription[] dfds = DFService.search(this, dfd);
		 if (dfds.length > 0 ) {
		    server = dfds[0].getName();
            System.out.println("Localized server");
		 }
		 else  System.out.println("Couldn't localize server!");
      }
      catch (Exception ex) {
	     ex.printStackTrace();
         System.out.println("Failed searching int the DF!");
	  }
   }


//--------------------------- Utility methods ----------------------------//


   Account findAccountByName(String name) {
// ----------------------------------------
      for (Iterator it = accounts.iterator(); it.hasNext();) {
         Account acc = (Account)it.next();
         if (acc.getName().equals(name))
			return acc;
	  }
      return null;
   }

   Account findAccountById(String id) {
// ----------------------------------------
      for (Iterator it = accounts.iterator(); it.hasNext();) {
         Account acc = (Account)it.next();
         if (acc.getId().equals(id))
			return acc;
	  }
      return null;
   }

   String chooseAccount() {
// ------------------------

	  if (accounts.isEmpty()) {
		 System.out.println("No account currently available!");
		 return null;
	  }
	  // Display account list
	  String out = "\n\t<<----- AVAILABLE ACCOUNT(S) ----->>\n";
	  for (Iterator it = accounts.iterator(); it.hasNext();)
	     out += "\n\t" + ((Account)it.next()).getName();

      return getUserInput(out + "\n\nAccount: ");
   }

   int getUserChoice() {
// ---------------------

      System.out.print("\n\t<<---- SAVINGS ACOUNT - MENU ---->>" +
                       "\n\n\t0. Terminate program" +
                       "\n\t1. Create a new account\n\t2. Make a deposit" +
                       "\n\t3. Make a withdrawal\n\t4. Get account balance" +
                       "\n\t5. Get list of operations\n> ");
      try {
		 BufferedReader buf = new BufferedReader(new InputStreamReader(System.in));
         String in = buf.readLine();
         return Integer.parseInt(in);
	  }
	  catch (Exception ex) { ex.printStackTrace(); }
	  return 0;
   }

   int getUserChoice(String msg) {
// -------------------------------

      System.out.print(msg);
      try {
		 BufferedReader buf = new BufferedReader(new InputStreamReader(System.in));
         String in = buf.readLine();
         return Integer.parseInt(in);
	  }
	  catch (Exception ex) { ex.printStackTrace(); }
	  return -1;
   }

   String getUserInput(String msg) {
// ---------------------------------

      System.out.print(msg);
      try {
		 BufferedReader buf = new BufferedReader(new InputStreamReader(System.in));
         return buf.readLine();
	  }
	  catch (Exception ex) { ex.printStackTrace(); }
	  return null;
   }

   void sendMessage(int performative, Object content) {
// ----------------------------------------------------

	  if (server == null) lookupServer();
	  if (server == null) {
		 System.out.println("Unable to localize the server! Operation aborted!");
		 return;
	  }
      ACLMessage msg = new ACLMessage(performative);
	  try {
		 msg.setContentObject((java.io.Serializable)content);
		 msg.addReceiver(server);
		 System.out.println("Contacting server... Please wait!");
		 send(msg);
	     addBehaviour(new WaitServerResponse(this));
	  }
	  catch (Exception ex) { ex.printStackTrace(); }
   }
}